/**
 * build_project
 * <p>
 * Copyright 2014 cayden.cn, Inc. All rights reserved.
 *
 * @author Administrator
 * @date 2021-09-26 09:25
 */
package com.cayden.config.project.componets.db;

import com.alibaba.druid.pool.DruidDataSource;
import com.cayden.base.base.db.weight.WeightUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.CollectionUtils;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

/**
 *
 * @author cayden
 * @date 2021-09-26 09:25
 */
public class MultipleDataSource extends AbstractRoutingDataSource {

    /**
     * 连接驱动
     */
    private String dbDriver;

    /**
     * 连接初始数
     */
    private int dbInitialSize;

    /**
     * 最小连接数
     */
    private int dbMinIdle;

    /**
     * 最大连接数
     */
    private int dbMaxActive;

    /**
     * 主主数据库配置
     */
    private String dbMasterConfig;

    /**
     * 从数据库配置
     */
    private List<String> dbSlaveConfig;

    /**
     * 原子类
     */
    private AtomicLong currSlaveIndex;

    /**
     * 分隔符
     */
    private String separator;

    /**
     * 主数据源
     */
    private DruidDataSource masterDataSource;

    /**
     * 子数据源
     */
    private List<DruidDataSource> slaveDataSources;

    /**
     * 多数据源配置
     */
    private MultipleDataSourcesProperties multipleDataSourcesProperties;

    public MultipleDataSource(String dbDriver, int dbInitialSize, int dbMinIdle, int dbMaxActive, String dbMasterConfig,
                              List<String> dbSlaveConfig,MultipleDataSourcesProperties multipleDataSourcesProperties) {
        this(dbDriver,dbInitialSize,dbMinIdle,dbMaxActive,dbMasterConfig,dbSlaveConfig,multipleDataSourcesProperties," ");
    }

    /**
     *
     * @param dbDriver
     * @param dbInitialSize
     * @param dbMinIdle
     * @param dbMaxActive
     * @param dbMasterConfig
     * @param dbSlaveConfig
     * @param separator 分隔符,这里支持任何分隔符,但是默认采用空格分割,对于多数据源支持有良好的修改
     */
    public MultipleDataSource(String dbDriver, int dbInitialSize, int dbMinIdle, int dbMaxActive,
                              String dbMasterConfig, List<String> dbSlaveConfig,MultipleDataSourcesProperties multipleDataSourcesProperties, String separator) {

        //开始基础参数校验 可以支持仅主库的情况
        if (StringUtils.isBlank(dbDriver)) {
            throw new IllegalArgumentException("dbDriver不能为空");
        } else if (StringUtils.isBlank(dbMasterConfig)) {
            throw new IllegalArgumentException("dbMasterConfig不能为空");
        }else if(separator == null || separator.length() == 0){
            throw new IllegalArgumentException("分隔符不能为空!");
        }

        // 参数初始化
        if (dbInitialSize <= 0) {
            dbInitialSize = 10;
        }

        if (dbMinIdle <= 0) {
            dbMinIdle = 10;
        }

        if (dbMaxActive <= 0) {
            dbMaxActive = 50;
        }


        this.dbDriver = dbDriver;
        this.dbInitialSize = dbInitialSize;
        this.dbMinIdle = dbMinIdle;
        this.dbMaxActive = dbMaxActive;
        this.dbMasterConfig = dbMasterConfig;
        this.dbSlaveConfig = dbSlaveConfig;
        this.currSlaveIndex = new AtomicLong(0L);
        this.multipleDataSourcesProperties = multipleDataSourcesProperties;
        this.separator = separator;
        List<DruidDataSource> dataSources = this.resolveDbConfig(this.dbMasterConfig);
        // 主库只能支持一个,对于多配置的主数据源,直接忽略
        if (dataSources.size() != 1){
            throw new IllegalArgumentException("目前只支持一个master");
        }

        this.masterDataSource = dataSources.get(0);

        // targetSourceMap init
        Map<Object,Object> dataSourcesMap = new HashMap<>(dbSlaveConfig.size() + 1);
        dataSourcesMap.put(this.masterKey(),masterDataSource);

        // 解析多数据源配置
        if (!CollectionUtils.isEmpty(dbSlaveConfig)){
            List<DruidDataSource> slaveDataSources = new ArrayList<>();
            for (int i = 0; i < dbSlaveConfig.size(); i++) {
                DruidDataSource dataSource = resolveDbConfig(dbSlaveConfig.get(i)).get(0);
                slaveDataSources.add(dataSource);
                dataSourcesMap.put(slaveKey(i),dataSource);
            }
            this.slaveDataSources = slaveDataSources;
        }

        this.setTargetDataSources(dataSourcesMap);
    }

    private List<DruidDataSource> resolveDbConfig(String dbConfig) {
        String[] split = dbConfig.split(this.separator);
        if (split.length != 0 && split.length % 3 == 0) {
            List<DruidDataSource> result = new ArrayList(split.length / 3);

            for(int i = 0; i < split.length; i += 3) {
                String dbUserName = split[i];
                String dbPasswd = split[i + 1];
                String dbUrl = split[i + 2];
                DruidDataSource dataSource = new DruidDataSource();
                dataSource.setDriverClassName(this.dbDriver);
                dataSource.setInitialSize(this.dbInitialSize);
                dataSource.setMinIdle(this.dbMinIdle);
                dataSource.setMaxActive(this.dbMaxActive);
                dataSource.setUrl(dbUrl);
                dataSource.setUsername(dbUserName);
                dataSource.setPassword(dbPasswd);
                dataSource.setMaxWait(60000L);
                dataSource.setTimeBetweenEvictionRunsMillis(60000L);
                dataSource.setMinEvictableIdleTimeMillis(300000L);
                dataSource.setValidationQuery("SELECT 'x'");
                dataSource.setTestWhileIdle(true);
                dataSource.setTestOnBorrow(false);
                dataSource.setTestOnReturn(false);
                result.add(dataSource);
            }

            return result;
        } else {
            throw new IllegalArgumentException("[" + dbConfig + "]格式有误");
        }
    }

    private String masterKey(){
        return "master";
    }

    private String slaveKey(int key) {
        return "slave_" + key;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = DynamicDataSourceHolder.getDataSouce();
        if (masterKey().equals(dataSource)){
            return masterKey();
        }

        if (slaveDataSources.size() == 1){
            return slaveKey(0);
        }

        // 对于指定库的来说,我们应该直接选择某个库
        if (StringUtils.isNotBlank(dataSource) && dataSource.contains("_")){
            String[] split = dataSource.split("_");
            return slaveKey(WeightUtils.chooseSlave(split,multipleDataSourcesProperties.getSlaveIndexHasZeoStart()));
        }

        Integer currSlaveIndex = WeightUtils.getSlaveKey(multipleDataSourcesProperties.getSlaveWeightConfig(),multipleDataSourcesProperties.getSlaveIndexHasZeoStart());
        return slaveKey(currSlaveIndex);
    }


    public void init() throws SQLException {
        masterDataSource.init();

        for (DruidDataSource slaveDataSource : slaveDataSources) {
            slaveDataSource.init();
        }
    }

    public void close() {
        masterDataSource.close();

        for (DruidDataSource slaveDataSource : slaveDataSources) {
            slaveDataSource.close();
        }
    }



}
